App.require(function () {
    App.defineWidgetBehavior("rotate", {
        render: function (ctx) {
            var target = this.target, pos = target.position, width = target.width, height = target.height;
            var r = Math.min(width, height) / 2 - 10;
            ctx.save();
            ctx.strokeStyle = "white";
            ctx.beginPath();
            var angle = this.target.rotate, renderAngle = angle > Math.PI * 2 ? Math.PI * 2 : angle;
            ctx.arc(0, 0, r, 0, renderAngle, false);
            ctx.stroke();
            var tPosX = r * Math.cos(renderAngle), tPosY = r * Math.sin(renderAngle);
            ctx.translate(tPosX, tPosY);
            ctx.rotate(renderAngle + Math.PI / 2);
            ctx.beginPath();
            ctx.moveTo(0, 0);
            ctx.lineTo(-15, 7.5);
            ctx.moveTo(0, 0);
            ctx.lineTo(-15, -7.5);
            ctx.stroke();
            ctx.fillStyle = "blue";
            ctx.beginPath();
            ctx.arc(0, 0, 5, 0, Math.PI * 2, false);
            ctx.fill();
            ctx.restore();
        },
        initialize: function () {
            this.data.isDown = false;
            this.data.startX = 0;
            this.data.startY = 0;
            this.data.angle = 0;
            this.attachDiyEvent("mousedown", this.mousedownHandler)
                .attachDiyEvent("mousemove", this.mousemoveHandler)
                .attachDiyEvent("mouseup", this.mouseupHandler);
            var target = this.target;
            var animation = new Animation(2000);
            
            animation.onUpdate(function (obj) {
                target.rotate = Math.PI * 2 * obj.elapsed;
            }).onComplete(function () {
                target.rotate = Math.PI * 2;
            });
            target.addAnimation(animation);
        },
        wrap: function (x) {
            var twoPi = 2.0 * Math.PI;
            while (x > 0.0) {
                x -= twoPi;
            }
            while (x < 0.0) {
                x += twoPi;
            }
            return x;
        },
        getEventRange: function (ev) {
            var pos = this.target._inverse(ev);
            var target = this.target, angle = target.rotate;
            var width = target.width, height = target.height;
            var r = Math.min(width, height) / 2 - 10, renderAngle = this.wrap(angle);
            var tPosX = r * Math.cos(renderAngle), tPosY = r * Math.sin(renderAngle);
            var deltaX = pos.x - tPosX, deltaY = pos.y - tPosY;
            return deltaX * deltaX + deltaY * deltaY < 25

        },
        mousedownHandler: function (ev) {
            if (this.getEventRange(ev)) {
                this.data.isDown = true;
                var pos = this.target._parentInverse(ev);
                this.data.startX = pos.x;
                this.data.startY = pos.y;
                this.data.angle = this.target.rotate;
            }
        },
        mousemoveHandler: function (ev) {
            if (this.data.isDown) {
                var target = this.target, pos = target.position, ev = this.target._parentInverse(ev);
                var oDeltaX = this.data.startX - pos.x, oDeltaY = this.data.startY - pos.y;
                var dDeltaX = ev.x - pos.x, dDeltaY = ev.y - pos.y;
                var angle = Math.acos((oDeltaX * dDeltaX + oDeltaY * dDeltaY) / 
                    (Math.sqrt(oDeltaY * oDeltaY + oDeltaX * oDeltaX) 
                   * Math.sqrt(dDeltaY * dDeltaY + dDeltaX * dDeltaX)));
                
                var direction = oDeltaX * dDeltaY - oDeltaY * dDeltaX;
                if (direction > 0) this.target.updatePropertyExcept("rotate", this.wrap(this.data.angle + angle), null);
                else this.target.updatePropertyExcept("rotate", this.wrap(this.data.angle - angle), null);
            }
        },
        mouseupHandler: function (ev) {
            this.data.isDown = false;
            this.data.startX = 0;
            this.data.startY = 0;
        },
        toForm: {
            rotate: {
                element: function () {
                    return {
                        type: "range",
                        min: 0,
                        max: Math.PI * 2,
                        step: 0.001
                    }
                },
                initValue: function () {
                    return this.rotate;
                },
                callback: function (key, value, element) {
                    this.updatePropertyExcept(key, parseFloat(value), element);
                }
            }
        }
    });
})